#include "Action.h"

#include <sys/types.h>
#include <dirent.h>

#include <iostream>
#include <fstream>

#include "GlobalParam.h"
#include "Typedef.h"
#include "BookInfoList.h"

#define DEFAULT_BOOK_STATUS BookUnread
#define DEFAULT_BOOK_PAGE_LAST_READ_TO 0

BPcode Action::ImportBooksFromDirectory(Bchar* ThisDirectory)
{
    DIR* ThisDir = NULL;
    struct dirent* Entry;
    string BookPath;
    Book* ThisBook = new Book("");
    BPcode ret;

    if(!ThisDatabase)
    {
        ret = BP_BAD;
        goto end;
    }

    ThisDir = opendir(ThisDirectory);
    if(!ThisDir)
    {
        ret = BP_BAD;
        goto end;
    }

    while(Entry=readdir(ThisDir))
    {
        if(Entry->d_name[0] == '.')
            continue;

        BookPath = ThisDirectory;
        BookPath += Entry->d_name;

        bplog(LOG_LEVEL_DEBUG,"Action:Beginning to import book %s",BookPath.c_str());

        ThisBook->Info.BookFilePath = BookPath;
        ThisBook->Info.BookName = BookInfo::BookFilePathToName(BookPath);

        if(ThisBook->Verify())
        {
            char* buffer = NULL;
            long size;
            ifstream file (ThisBook->Info.BookFilePath.c_str(),ios::in|ios::binary|ios::ate);
            size = file.tellg();
            if(size > BOOK_DATA_LENGTH_FOR_MAKING_DIGEST)
                size = BOOK_DATA_LENGTH_FOR_MAKING_DIGEST;
            file.seekg (0, ios::beg);
            buffer = new char[size];
            file.read (buffer,size);
            file.close();

            ThisDigest->MakeDigest((Bupchar)buffer,size);
            bplog(LOG_LEVEL_DEBUG,"Action:Digest of this book is %s",ThisBook->Info.BookDigest.digest_hex.c_str());

            delete[] buffer;

            ThisBook->Info.BookDigest.digest_hex = ThisDigest->GetDigestHex();

            ThisBook->Info.PageCount = 0;
            ThisBook->Info.ISBN = "";
            ThisBook->Info.Author = "";
            ((DBSqlite *)ThisDatabase)->AddBook(ThisBook);
        }
    }

end:
    if(ThisDir)
        closedir(ThisDir);
    if(ThisBook)
       delete ThisBook;

    return ret;
}

BPcode Action::RefreshBooksFromDirectory()
{
    DIR* ThisDir = NULL;
    struct dirent* Entry;
    string BookPath;
    string SearchPath;
    Book* ThisBook = new Book("");
    Bchar* ThisDirectory = NULL;
    BPcode ret;

    if(!ThisDatabase)
    {
        ret = BP_BAD;
        goto end;
    }

    SearchPath = ((DBSqlite *)ThisDatabase)->GetSearchDirectory();
    ThisDirectory = (char *)SearchPath.c_str();

    ThisDir = opendir(ThisDirectory);
    if(!ThisDir)
    {
        ret = BP_BAD;
        goto end;
    }

    ((DBSqlite *)ThisDatabase)->SetAllBooksNonExisted();

    while(Entry=readdir(ThisDir))
    {
        if(Entry->d_name[0] == '.')
            continue;

        BookPath = SearchPath;
        BookPath += "\/";
        BookPath += Entry->d_name;

        bplog(LOG_LEVEL_DEBUG,"Action:Beginning to import book %s",BookPath.c_str());

        ThisBook->Info.BookFilePath = BookPath;
        ThisBook->Info.BookName = BookInfo::BookFilePathToName(BookPath);

        if(ThisBook->Verify())
        {
            char* buffer = NULL;
            long size;
            ifstream file (ThisBook->Info.BookFilePath.c_str(),ios::in|ios::binary|ios::ate);
            size = file.tellg();
            if(size > BOOK_DATA_LENGTH_FOR_MAKING_DIGEST)
                size = BOOK_DATA_LENGTH_FOR_MAKING_DIGEST;
            file.seekg (0, ios::beg);
            buffer = new char[size];
            file.read (buffer,size);
            file.close();

            ThisDigest->MakeDigest((Bupchar)buffer,size);
            bplog(LOG_LEVEL_DEBUG,"Action:Digest of this book is %s",ThisBook->Info.BookDigest.digest_hex.c_str());

            delete[] buffer;

            ThisBook->Info.BookDigest.digest_hex = ThisDigest->GetDigestHex();

            ThisBook->Info.PageCount = 0;
            ThisBook->Info.ISBN = "";
            ThisBook->Info.Author = "";
            ((DBSqlite *)ThisDatabase)->AddBook(ThisBook);
        }
    }

end:
    if(ThisDir)
        closedir(ThisDir);
    if(ThisBook)
       delete ThisBook;

    return ret;
}

void Action::ReadPage(uint32_t Page, string Keyword)
{
    uint32_t ret = 0;

    bplog(LOG_LEVEL_INFO,"Action:in function ReadPage,keyword is %s",Keyword.c_str());

    CurrentKeyword = Keyword;

    ret = ((DBSqlite*)ThisDatabase)->RefreshBookInfoList(Page,g_pool.InfoList->GetListPtr(),BOOK_INFO_LIST_LENGTH,Keyword);
    g_pool.InfoList->SetListBookCount(ret);
    g_pool.InfoList->SetCurrentPage(Page);
}

void Action::ReadNextPage()
{
    uint32_t page = g_pool.InfoList->GetCurrentPage();

    ReadPage(page+1, CurrentKeyword);
}

void Action::ReadPreviousPage()
{
    uint32_t page = g_pool.InfoList->GetCurrentPage();

    if(page-1 < 1)
    {
        bplog(LOG_LEVEL_WARN,"Action:the page you wanted is %d, which is below 1 and is invalid, request aborted",page);
        return;
    }

    ReadPage(page-1, CurrentKeyword);
}

Book* Action::GetBookAndHistorysFromPoolOrInsert(uint32_t BookID)
{
    Book* ThisBook = NULL;

    ThisBook = GetBookFromPoolOrDatabase(BookID);
    if(ThisBook == NULL)
    {
        bplog(LOG_LEVEL_ERROR,"Action:Can't find the book in database by id %d",BookID);
        return NULL;
    }

    if(ThisBook->GetIsHistorysGot())
    {
        bplog(LOG_LEVEL_INFO,"Action:This book has got the hitorys,return this book from pool directly");
        return ThisBook;
    }

    ThisBook->ClearHistorys();
    ((DBSqlite *)ThisDatabase)->GetHistorysByBookID(ThisBook->Info.BookID,ThisBook->GetHistorysPtr());
    ThisBook->SetIsHistorysGot(true);

    return ThisBook;
}

Book* Action::RefreshHistorysFromDatabase(uint32_t BookID)
{
    Book* ThisBook = NULL;

    ThisBook = GetBookFromPoolOrDatabase(BookID);
    if(ThisBook == NULL)
    {
        bplog(LOG_LEVEL_ERROR,"Action:Can't find the book in database by book id %d",BookID);
        return NULL;
    }

    if(ThisBook->GetIsHistorysGot())
        ThisBook->ClearHistorys();

    ((DBSqlite *)ThisDatabase)->GetHistorysByBookID(ThisBook->Info.BookID,ThisBook->GetHistorysPtr());
    ThisBook->SetIsHistorysGot(true);

    return ThisBook;
}

Book* Action::GetBookFromPoolOrDatabase(uint32_t BookID)
{
    Book* ThisBook = NULL;
    Bbool IsGotFromPool = false;

    ThisBook = g_pool.FindBookByID(BookID);
    if(ThisBook)
        IsGotFromPool = true;

    if(ThisBook == NULL)
        ThisBook = ((DBSqlite *)ThisDatabase)->GetBookByID(BookID);

    if(ThisBook == NULL)
        return NULL;

    if(!IsGotFromPool)
        g_pool.InsertThisBook(ThisBook);

    return ThisBook;
}

BPcode Action::AddHisotryIntoTheBookNPool(uint32_t BookID, History* NewHistory)
{
    Book* ThisBook = NULL;
    BPcode rv;

    ThisBook = GetBookAndHistorysFromPoolOrInsert(BookID);
    if(ThisBook == NULL)
        return BP_BAD;

    rv = ((DBSqlite *)ThisDatabase)->AddHistory(NewHistory,BookID);
    if(rv != BP_OK)
    {
        bplog(LOG_LEVEL_ERROR,"Action:adding history to database is failed, book id=%d",BookID);
        return BP_BAD;
    }

    rv = ThisBook->AddHistory(NewHistory);
    if(rv != BP_OK)
    {
        bplog(LOG_LEVEL_ERROR,"Action:adding history to book in pool is failed, book id=%d",BookID);
        return BP_BAD;
    }

    return BP_OK;
}

BPcode Action::DeleteHistoryFromBookNPool(uint32_t BookID,uint32_t HistoryID)
{
    Book* ThisBook = NULL;
    BPcode rv;

    ThisBook = GetBookAndHistorysFromPoolOrInsert(BookID);
    if(ThisBook == NULL)
        return BP_BAD;

    rv = ((DBSqlite *)ThisDatabase)->DeleteHistory(HistoryID);
    if(rv != BP_OK)
    {
        bplog(LOG_LEVEL_ERROR,"Action:deleting history from database is failed, book id=%d",BookID);
        return BP_BAD;
    }

    rv = ThisBook->DeleteHistoryByID(HistoryID);
    if(rv != BP_OK)
    {
        bplog(LOG_LEVEL_ERROR,"Action:deleting history from pool is failed, book id=%d",BookID);
        return BP_BAD;
    }

    return BP_OK;
}
